﻿/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using NTasking.Internals;

namespace NTasking
{
    // ReSharper disable InconsistentNaming

    // Contains various classes and extension methods which allow to implement the fluent syntax
    // for writing a selective accept
    //
    // The SA abreviation stands for SelectiveAccept

    /// <summary>
    /// Define extension methods which allow to implement the fluent syntax for writing a selective accept
    /// </summary>
    public static partial class SA
    {

        // ReSharper disable UnusedTypeParameter

        #region Internal Classes

        public interface IDelay<T>
            where T : IDelay<T> { }


        public interface IRelativeDelay : IDelay<IRelativeDelay> { }


        public interface IAbsoluteDelay : IDelay<IAbsoluteDelay> { }


        public interface IAnyDelay : IDelay<IAnyDelay>, IRelativeDelay, IAbsoluteDelay { }


        public interface IComplete { }


        public interface IBeforeElse { }


        public interface IBeforeThen<T>
            where T : Base, new() { }


        public interface IBeforeAccept<T>
            where T : Base, new() { }


        public interface IBeforeAccept<T, IndexT> : IBeforeAccept<T>
            where T : Base, new() { }


        public interface IBeforeWhen<T>
            where T : Base, new() { }


        public interface IBeforeIndexedWhen<T>
            where T : Base, new() { }


        public class Base
        {
            internal SelectiveAccept Select { get; set; }
        }


        public class BeforeOr<T> : Base
            where T : Base, new()
        {

            public T Or { get { return new T { Select = Select }; } }
        }


        public class BeforeOrWhenDelay<T> : BeforeOr<BeforeWhenDelay<T>>
            where T : IDelay<T> { }


        public class BeforeOrWhenAny : BeforeOr<BeforeWhenAny> { }


        public class BeforeDelay<T> : Base, IDelay<T>
            where T : IDelay<T> { }



        public class BeforeAccept<T> : Base, IBeforeAccept<T>
            where T : Base, new() { }


        public class BeforeWhenAccept<T> : BeforeAccept<T>, IBeforeWhen<BeforeAccept<T>>
            where T : Base, new() { }


        public class BeforeAccept<IndexT, T> : Base, IBeforeAccept<T, IndexT>
            where T : Base, new() { }


        public class BeforeAny : BeforeAccept<AfterNextAccept>, IAnyDelay
        {
            /// <summary>
            /// Add a terminate alternative.
            /// </summary>
            /// <value></value>
            public AfterTerminate Terminate
            {
                get
                {
                    Select.HasTerminate = true;

                    return new AfterTerminate { Select = Select };
                }
            }
        }


        public class AfterTerminate : Base, IComplete { }


        public class BeforeWhenAny : BeforeAny, IBeforeWhen<BeforeAny>, IBeforeIndexedWhen<AfterNextAccept> { }


        public class AfterSelect : BeforeWhenAccept<AfterFirstAccept>, IBeforeIndexedWhen<AfterFirstAccept> { }


        public class AfterFirstAcceptContinuation : BeforeOrWhenAny, IBeforeElse { }


        public class AfterFirstAccept : AfterFirstAcceptContinuation, IBeforeThen<AfterFirstAcceptContinuation> { }


        public class AfterNextAcceptContinuation : AfterFirstAcceptContinuation, IComplete { }


        public class AfterNextAccept : AfterNextAcceptContinuation, IBeforeThen<AfterNextAcceptContinuation> { }


        public class BeforeWhenDelay<T> : BeforeDelay<T>, IBeforeWhen<BeforeDelay<T>>
            where T : IDelay<T> { }


        public class AfterDelayContinuation<T> : BeforeOrWhenDelay<T>, IComplete
            where T : IDelay<T> { }


        public class AfterDelay<T> : AfterDelayContinuation<T>, IBeforeThen<AfterDelayContinuation<T>>
            where T : IDelay<T> { }


        public class AfterElse : Base, IComplete { }

        #endregion


        #region Implementation of IRelativeDelay

        /// <summary>
        /// Add a delay alternative.
        /// </summary>
        /// <param name="delay">The current instance</param>
        /// <param name="duration">The delay duration.</param>
        /// <returns></returns>
        public static AfterDelay<IRelativeDelay> Delay(this IDelay<IRelativeDelay> delay, TimeSpan duration)
        {
            SelectiveAccept s = ((Base)delay).Select;

            Timeout timeout = new Timeout(duration) { Barrier = s.CurrentSimpleBarrier };

            if (timeout.IsOpened() && s.Timeout.IsLater(timeout))
                s.Timeout = timeout;

            s.CurrentContinuable = timeout;

            return new AfterDelay<IRelativeDelay> { Select = s };
        }


        /// <summary>
        /// Add a delay alternative.
        /// </summary>
        /// <param name="delay">The current instance</param>
        /// <param name="milliseconds">The delay duration in milliseconds.</param>
        /// <returns></returns>
        public static AfterDelay<IRelativeDelay> Delay(this IDelay<IRelativeDelay> delay, int milliseconds)
        {
            SelectiveAccept s = ((Base)delay).Select;

            Timeout timeout = new Timeout(milliseconds) { Barrier = s.CurrentSimpleBarrier };

            if (timeout.IsOpened() && s.Timeout.IsLater(timeout))
                s.Timeout = timeout;

            s.CurrentContinuable = timeout;

            return new AfterDelay<IRelativeDelay> { Select = s };
        }


        /// <summary>
        /// Add a delay alternative.
        /// </summary>
        /// <param name="delay">The current instance</param>
        /// <param name="seconds">The delay duration in seconds.</param>
        /// <returns></returns>
        public static AfterDelay<IRelativeDelay> Delay(this IDelay<IRelativeDelay> delay, double seconds)
        {
            SelectiveAccept s = ((Base)delay).Select;

            Timeout timeout = new Timeout(seconds) { Barrier = s.CurrentSimpleBarrier };

            if (timeout.IsOpened() && s.Timeout.IsLater(timeout))
                s.Timeout = timeout;

            s.CurrentContinuable = timeout;

            return new AfterDelay<IRelativeDelay> { Select = s };
        }

        #endregion


        #region Implementation of IAbsoluteDelay

        /// <summary>
        /// Add a delay alternative.
        /// </summary>
        /// <param name="delay">The current instance</param>
        /// <param name="date">The delay end.</param>
        /// <returns></returns>
        public static AfterDelay<IAbsoluteDelay> Delay(this IDelay<IAbsoluteDelay> delay, DateTime date)
        {
            SelectiveAccept s = ((Base)delay).Select;

            Timeout timeout = new Timeout(date) { Barrier = s.CurrentSimpleBarrier };

            if (timeout.IsOpened() && s.Timeout.IsLater(timeout))
                s.Timeout = timeout;

            s.CurrentContinuable = timeout;

            return new AfterDelay<IAbsoluteDelay> { Select = s };
        }

        #endregion


        #region Implementation of IBeforeThen

        /// <summary>
        /// Add continuation statements.
        /// </summary>
        /// <typeparam name="T">The type returned</typeparam>
        /// <param name="iba">The current instance</param>
        /// <param name="continuation">The continuation statements.</param>
        /// <returns></returns>
        public static T Then<T>(this IBeforeThen<T> iba, Action continuation)
            where T : Base, new()
        {
            SelectiveAccept s = ((Base)iba).Select;

            s.CurrentContinuable.Continuation = continuation;

            return new T { Select = s };
        }

        #endregion


        #region Implementation of IBeforeWhen<T>

        /// <summary>
        /// Add a simple barrier.
        /// </summary>
        /// <typeparam name="T">The return type</typeparam>
        /// <param name="ibw">The current instance</param>
        /// <param name="barrier">The barrier.</param>
        /// <returns></returns>
        public static T When<T>(this IBeforeWhen<T> ibw, Func<bool> barrier)
            where T : Base, new()
        {
            SelectiveAccept s = ((Base)ibw).Select;

            //if (barrier == null)
            //{
            //    s.Task.OnSelectSyntaxError();
            //    throw Errors.BarrierIsNull();
            //}

            s.CurrentSimpleBarrier = barrier;

            return new T { Select = s };
        }

        #endregion


        #region Implementation of IBeforeIndexedWhen<T, IndexT>

        /// <summary>
        /// Add an indexed barrier.
        /// </summary>
        /// <typeparam name="IndexT">The type of the index.</typeparam>
        /// <typeparam name="T">The return type</typeparam>
        /// <param name="ibiw">The current instance</param>
        /// <param name="indexedBarrier">The indexed barrier.</param>
        /// <returns></returns>
        public static BeforeAccept<IndexT, T> When<IndexT, T>(this IBeforeIndexedWhen<T> ibiw, Func<IndexT, bool> indexedBarrier)
            where T : Base, new()
        {
            SelectiveAccept s = ((Base)ibiw).Select;

            //if (indexedBarrier == null)
            //{
            //    s.Task.OnSelectSyntaxError();
            //    throw Errors.BarrierIsNull();
            //}

            s.SetCurrentIndexedBarrier(indexedBarrier);

            return new BeforeAccept<IndexT, T> { Select = s };
        }

        #endregion


        #region Implementation of IBeforeElse

        /// <summary>
        /// Add an else alternative.
        /// </summary>
        /// <param name="ibe">The current instance</param>
        /// <param name="elseAlternative">The else alternative.</param>
        /// <returns></returns>
        public static AfterElse Else(this IBeforeElse ibe, Action elseAlternative)
        {
            SelectiveAccept s = ((Base)ibe).Select;

            if (elseAlternative == null)
                s.Else = () => { };
            else
                s.Else = elseAlternative;

            return new AfterElse { Select = s };
        }

        #endregion


        #region Helper methods for IBEforeAccept implementation

        // TODO: comment all the following methods
        private static ExecutionContext AcceptAny<ExecuteDelegateT>(SelectiveAccept s, IExecutable executable, ExecuteDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where ExecuteDelegateT : class
        {
            // Check that the select is not within the abortable part of a selective call
            if (ThreadWrapper.CurrentThread.InAbortablePart)
                throw Errors.AcceptNotAllowedInAbortablePart(Task.CurrentTask);

            if (checkBody && body == null)
                throw Errors.BodyIsNull(executable);

            // ReSharper disable PossibleNullReferenceException
            if (body != null && (body as MulticastDelegate).Target as IExecutable != null)
                throw Errors.AcceptBodyIsSynchronized(s.Task, (body as MulticastDelegate).Target as SynchronizedMethod);
            // ReSharper restore PossibleNullReferenceException

            if (executable.Family.CurrentlyBeingAccepted)
                throw Errors.InvalidNestedAccept(s.Task, executable.Family);

            if (executable.Family.Owner != s.Task)
                throw Errors.AcceptCallNotOwned(executable.Family);

            ExecutionContext context = executable.CreateContext();
            context.ExceptionHandler = exceptionHandler;
            context.Barrier = s.CurrentSimpleBarrier;

            s.CurrentContinuable = context;

            if (!context.IsClosed)
            {
                s.OpenEntries += 1;
                context.Target.AcceptableCalls.Add(context);
            }

            if (body != null)
                ((ExecutionContext<ExecuteDelegateT>)context).Body = body;

            return context;
        }


        internal static ExecutionContext AcceptEntry<CallDelegateT>(SelectiveAccept s, CallDelegateT call, CallDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where CallDelegateT : class
        {
            if (call == null)
                throw Errors.EntryIsNull();

            // ReSharper disable PossibleNullReferenceException
            var executable = (call as MulticastDelegate).Target as IExecutable;
            // ReSharper restore PossibleNullReferenceException

            if (executable == null)
                // ReSharper disable PossibleNullReferenceException
                throw Errors.AcceptCallNotSynchronized(s.Task, (call as MulticastDelegate).Target);
            // ReSharper restore PossibleNullReferenceException

            return AcceptAny(s, executable, body, checkBody, exceptionHandler);
        }


        private static T AcceptEntry<T, CallDelegateT>(SelectiveAccept s, CallDelegateT call, CallDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where T : Base, new()
            where CallDelegateT : class
        {
            AcceptEntry(s, call, body, checkBody, exceptionHandler);
            return new T { Select = s };
        }


        internal static ExecutionContext AcceptFamily<IndexT, ExecuteDelegateT>(SelectiveAccept s, IExecutable family, ExecuteDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where ExecuteDelegateT : class
        {
            if (family == null)
                throw Errors.EntryIsNull();

            ExecutionContext context = AcceptAny(s, family, body, checkBody, exceptionHandler);

            ((IGuardable<IndexT>)context).IndexedBarrier = s.GetCurrentIndexedBarrier<IndexT>();

            return context;
        }


        private static T AcceptFamily<T, IndexT, ExecuteDelegateT>(SelectiveAccept s, IExecutable family, ExecuteDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where T : Base, new()
            where ExecuteDelegateT : class
        {
            AcceptFamily<IndexT, ExecuteDelegateT>(s, family, body, checkBody, exceptionHandler);
            return new T { Select = s };
        }


        internal static ExecutionContext AcceptMember<IndexT, CallDelegateT, ExecuteDelegateT>(SelectiveAccept s, CallDelegateT call, ExecuteDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where CallDelegateT : class
            where ExecuteDelegateT : class
        {
            if (call == null)
                throw Errors.EntryIsNull();

            // ReSharper disable PossibleNullReferenceException
            var executable1 = (call as MulticastDelegate).Target as IExecutable;
            // ReSharper restore PossibleNullReferenceException

            // Check that the provided entry is an executable entity (entry or entry family member)
            if (executable1 == null)
                // ReSharper disable PossibleNullReferenceException
                throw Errors.AcceptCallNotSynchronized(s.Task, (call as MulticastDelegate).Target);
            // ReSharper restore PossibleNullReferenceException

            // ReSharper disable PossibleNullReferenceException
            var executable2 = (call as MulticastDelegate).Target as IExecutable<ExecuteDelegateT>;
            // ReSharper restore PossibleNullReferenceException

            // Check that the provided entry is an executable entity with correct body profile
            if (executable2 == null)
                throw Errors.IndexTypeMismatch(s.Task, executable1.Family);

            ExecutionContext execute = AcceptAny(s, executable1, body, checkBody, exceptionHandler);

            ((IGuardable<IndexT>)execute).IndexedBarrier = s.GetCurrentIndexedBarrier<IndexT>();

            return execute;
        }


        private static T AcceptMember<T, IndexT, CallDelegateT, ExecuteDelegateT>(SelectiveAccept s, CallDelegateT call, ExecuteDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where T : Base, new()
            where CallDelegateT : class
            where ExecuteDelegateT : class
        {
            AcceptMember<IndexT, CallDelegateT, ExecuteDelegateT>(s, call, body, checkBody, exceptionHandler);
            return new T { Select = s };
        }

        #endregion

    }

}
